/*
 * Progetto PPS2012 -seegwords
 *
 * Ettore Chimenti
 * Giuliano De Angelis
 * 
 * @02.2013
 * 
 * 4) Realizzare un mini motore di ricerca, implementato attraverso una lista inversa:
 * http://en.wikipedia.org/wiki/Inverted_index
 * 
 * Leggere un insieme di documenti .txt da una directory. Si crei una classe MotoreDiRicerca che 
 * permette di effettuare ricerche efficienti realizzando una lista inversa.
 * In particolare, la classe prende i documenti e crea una la lista inversa:
 * parola->lista_documenti_che_la_contengono
 * 
 * Implementare un metodo Search che prende in ingresso una stringa e ritorna un vettore di documenti.
 * In particolare, il metodo Search separa la stringa in un insieme di parole. I documenti ritornati 
 * sono trovati intersecando le liste relative alle parole. 
 * 
 */
#include <iostream>
#include<map>
#include<set>
#include<list>
#include<fstream>
#include<sstream>
#include<string>
#include<vector>
#include<boost/regex.hpp>
#include<boost/filesystem.hpp>
#include "./MotoreDiRicerca.h"

#define BOOST_FILESYSTEM_NO_DEPRECATED

using namespace std;
namespace fs = boost::filesystem; 

bool 		MotoreDiRicerca::ScanFile	(const string&		file, const int& i){

    boost::regex expression("^([^A-Za-z0-9]*)|([^A-Za-z0-9]*)$") ;	// punteggiatura in testa o in coda alla parola
    string word;
    int k=0;
    
    ifstream is(file.c_str());
    if (!is.good()) return false;
    
    while (!is.eof()){			 				// lettura 
      is >> word;
      
      transform(word.begin(), word.end(), word.begin(), ::tolower);					//tutto lowercase
      word = boost::regex_replace(word, expression, "", boost::match_default | boost::format_sed); 	//togliere la punteggiatura 
      
      db.insert(make_pair(word,pos(i,k)));				// associazione db
      k++;
    }
   return true;
} 

bool 		MotoreDiRicerca::ScanDir	(const string&  arg, vector<string>* doc_names_){
  if (fs::exists( arg )){						// il file esiste?
    if ( fs::is_directory( arg )){					// se directory
      fs::path p(arg);
      vector<fs::path> path_vec;
	copy(fs::directory_iterator(p), fs::directory_iterator(), back_inserter(path_vec));	// inserisci i file dentro un vettore
	for (vector<fs::path>::iterator it = path_vec.begin() ; it != path_vec.end(); it++) 
	  ScanDir(it->string(), doc_names_);							// esegui funz ricorsiva
    } else if ( fs::path( arg ).extension() == ".txt" )							// se invece file
      doc_names_->push_back( arg );								// inserisci nel vettore
  } else
    return false;
  return true;
}
  
bool 		MotoreDiRicerca::Index		(const vector<string>&  args_){
  
  if ( &args_ == NULL || args_.size() == 0 )
    return false;
  
  vector<string> doc_names_;
  
  for (unsigned int i=0 ; i<args_.size() ; i++) {
    if ( !ScanDir(args_[i],&doc_names_) )			// Scansiono gli argomenti
      return false;
  } 
  
  doc_names = doc_names_;
  
  db.clear();
  
  for ( unsigned int i=0; i<doc_names.size(); i++) {		// Leggo i file e popolo la mappa
    if ( !ScanFile(doc_names[i],i) )
      return false;
  }
  
  if (db.size() == 0)
    return false;
  
  return true;
 
}

list<Word*>* 	MotoreDiRicerca::Parser		(const string& 		query)const{
  
  list  <Word*>*	wordlist	= new list<Word*>;
  list  <Word*>         notlist;
  
  boost::regex expression("\\s{2,}") ;

  string query_ = boost::regex_replace(query, expression, " ", boost::match_default | boost::format_sed); 	//togliere la punteggiatura 
  
  if (query_[0] == ' ') {
    query_.erase(0,1);
  }      

  istringstream 	ss(query_);
  string 		item;
 
  while(std::getline(ss, item, ' ')) {
    char		OP		= 'A';	  // Add di default
    bool		VIRG 		= 0 ;									
    vector<string> 	cont;
    
    //parsing
    if (item[0] == '-') {
      OP = 'N';
      item.erase(0,1);
    }      
    if (item == "OR" || item == "||" || item == "|") {
      OP = 'O';
      std::getline(ss, item, ' ');      
    }
    if (item == "+") {
      OP = 'A';
      std::getline(ss, item, ' ');      
    }
    if (item[0] == '\"'){
      VIRG = 1;
      item.erase(item.begin());
    }
    
    transform(item.begin(), item.end(), item.begin(), ::tolower);	//tutto lowercase

    cont.push_back(item);						// inserisco nel vettore di query
    
    while( VIRG && std::getline(ss, item, ' ') ){			//"w1 w2..."
      
      if (item[item.size()-1] == '\"'){	 
	VIRG = 0;
	item.erase(item.end()-1);
      }
      cont.push_back(item);
    }    
    
    //case
    
    if (OP == 'N')
      notlist.push_back(new Not(Find(cont)));
    else if (OP == 'O')
      wordlist->push_back(new Or(Find(cont)));
    else if (OP == 'A')
      wordlist->push_front(new And(Find(cont)));
    
  }
  
  for(list<Word*>::const_iterator it = notlist.begin(); it != notlist.end(); it++)
    wordlist->push_back(*it);
  
  return wordlist;
}

set<int>* 	MotoreDiRicerca::Find_Mono	(const string& 		query)const{
  
  // parola SINGOLA
  set	   <int>* 			 	results = new set<int>;
  multimap <string, pos> ::const_iterator 	iter_db;

  iter_db=db.find(query);						// find db
  
  if (iter_db == db.end())						// parola non trovata
    return results;						
  
  while ( iter_db != db.end() && iter_db->first == query ){		// finche' resta la stessa parola 
    results->insert(iter_db->second.document);				// insert(documento) nel set 
    iter_db++;
  } 
  
  return results;						

}

set<int>* 	MotoreDiRicerca::Find_Multi	(const vector<string>& 	query)const{
  
   // parole multiple
   set	   <int>* 			 	results = new set<int>;
   multimap <string, pos> ::const_iterator 	iter_db;
   
   multimap  <int,  int>			doc_pos;
   multimap  <int,  int> ::const_iterator 	iter_pos;
   
   iter_db=db.find(query[0]);						// find db PRIMA PAROLA
   
   if (iter_db == db.end())						// parola non trovata
     return results;						
   
   while ( iter_db != db.end() && iter_db->first == query[0] ){	
     doc_pos.insert(make_pair(iter_db->second.document,iter_db->second.position));	// Inserisco la coppia doc,pos di ogni hit dentro la mappa 	
     iter_db++;
   }   
   
   for (unsigned int i=1 ; i<query.size(); i++){					// CICLO PAROLE SUCCESSIVE
     multimap  <int, int>		 	doc_pos_tmp;
     
     if (doc_pos.size()==0)
       return results;
     
     iter_db = db.find(query[i]);						// find db PAROLE SUCCESSIVE
     
     if (iter_db == db.end())						// parola non trovata
       return results;
     
     while ( iter_db != db.end() && iter_db->first == query[i] ){   	// per ogni documento 
       
       int doc = iter_db->second.document;				// doc = documento che contiene la parola 
       int posit = iter_db->second.position;				// posit = posizione della parola
       
       iter_pos = doc_pos.find( doc );
       
       while ( iter_pos != doc_pos.end() && iter_pos->first == doc ){ 				// (DOC,pos) == (word,(DOC,pos))    
	 if (iter_pos->second+1 == posit ){				// se la parola attuale e' successiva nello stesso doc 
	   doc_pos_tmp.insert( make_pair( doc , posit )) ;		// inserisco nella mappa temporanea la coppia (doc, posit)
	   break;
	 }
	 iter_pos++;
      }	
     iter_db++;
     }
   doc_pos = doc_pos_tmp;						// si aggiorna la mappa di confronto 
  }

for ( iter_pos = doc_pos.begin();					// carico i risultati nel set
      iter_pos != doc_pos.end();
      iter_pos++ )
     results->insert(iter_pos->first);
     
return results;
     
}

set<int>* 	MotoreDiRicerca::Find		(const vector<string>& 	query)const{  
  
  if ( query.size() == 0 )
    return new set<int>;
  
  if ( query.size() != 1 )
    return Find_Multi(query);
  
  return Find_Mono(query[0]);
}

vector<string>* MotoreDiRicerca::Int2NameFile	(const set<int>& 	int_docs)const{
  
  vector<string>*		namefiles = new vector<string>;
  set	<int>	::iterator	iter 	  = int_docs.begin();
  
  if ( int_docs.size() == 0 )
    return namefiles;
  
  for (; iter != int_docs.end(); ++iter )
    namefiles->push_back(doc_names[*iter]);
  
  return namefiles;
}

vector<string>* MotoreDiRicerca::Search		(const string& 		query)const{
  
  if (query == "")
    return new vector<string>;
  
  list	<Word*>* 		wordlist = Parser(query); 
  list	<Word*>::iterator	op 	 = wordlist->begin();					// Operazione
  set	<int> 			results  = (dynamic_cast<Not*>(*op))?results:(**op).GetDocs();	// Inizializzo alla prima parola
   
  delete (*op);
  
  for (op++; op != wordlist->end() ; op++) {
    (**op).Operation(results);
    delete (*op);
  } 
   
  delete wordlist;
  
  vector<string>* namefiles 		 = Int2NameFile(results);   
  
  return namefiles;
}

unsigned int 	MotoreDiRicerca::GetDbSize	()const{return db.size(); }

unsigned int	MotoreDiRicerca::GetDocSize	()const{return doc_names.size(); }